import { ReactiveCommand } from './../libs/rxjs/cc3/ReactiveCommand';
import { ReactiveProperty } from './../libs/rxjs/cc3/ReactiveProperty';
import { BaseManager } from './../libs/rxjs/cc3/BaseManager';
import { _decorator, color, director, System, sys } from 'cc';
import { i18n } from '../i18n';
import { DevConfig, IapKey, ItemKey } from './DevConfig';
import { gm } from './GameManager';
import { timem } from './TimeManager';
import { IapProductInfo, ProvideInfo } from './SDK/IapSDKManager';
import { game } from 'cc';
import { thinking_sdk_m } from './SDK/ThinkingCocosSDKManager';
import { UUIDTools } from '../libs/uuid/UUIDTools';
import { misc_sdk_m } from './SDK/MiscSDKManager';
import { jsb_sdk_m } from './SDK/JsbSDKManager';
import { Merge, MergeASAP } from '../libs/rxjs/cc3/Merge';
import { appsflyer_sdk_m } from './SDK/AppsFlyerSDKManager';
import { cloudstorage_sdk_m } from './SDK/CloudStorageSDKManager';
import { notification_sdk_m } from './SDK/NotificationCocosSDKManager';
import { randomChoose } from '../libs/utils/RandomChooseTools';

// 跨包引用，只有在lobby包加载后才能使用声明的对象
import { StageInfo } from '../../ab_lobby/scripts/game/basic';
import { taskm } from './TaskManager';
import { pfm } from './PlayFabManager';

const { ccclass, property } = _decorator;

// 关卡统计项
export type StageStat = {
    startTimes?: number,
}

export type IapStat = {
    buyNum?: number, // 已经购买次数
    buyAt?: number, // 上次购买时间
}

export type FreeStat = {
    freeNum?: number, // 已经购买次数
    freeAt?: number, // 上次看广告时间
}

export type SavingPotInfo = {
    coin: number, // 金币数量
    expAt: number, // 过期时间
    autoPopup: boolean, // 需要自动弹出标记
}

@ccclass('UserDataManager')
export class UserDataManager extends BaseManager {

    public static instance: UserDataManager;
    static ID = 'userdata_system';
    get logColorOverride() { return color().fromHEX('#EEDDDD') };

    localstorageKey = 'profile_108';

    CUR_DATA_VER = 1; // 当前玩家数据的结构版本

    static registerSystem() {
        director.unregisterSystem(director.getSystem(UserDataManager.ID));
        director.registerSystem(UserDataManager.ID, udm, System.Priority.MEDIUM);
        udm.init();
    }

    onInitManager() {
        this.log('onInitManager', this.timestamp);
        this.load();
    }

    v = ReactiveProperty.Create(0);
    dataVer = ReactiveProperty.Create(0); // 表示玩家数据的结构版本

    uid = ReactiveProperty.Create<string>(null);

    displayName = ReactiveProperty.Create<string>(null);

    bgmOn = ReactiveProperty.Create(true);
    bgmVolume = ReactiveProperty.Create(1);
    sfxOn = ReactiveProperty.Create(true);
    sfxVolume = ReactiveProperty.Create(1);
    vibrateOn = ReactiveProperty.Create(true);
    fullScreenOn = ReactiveProperty.Create(false);
    lang = ReactiveProperty.Create('en');
    langChangeCommand = ReactiveCommand.Create<string>();

    stageNum = ReactiveProperty.Create(1);
    heart = ReactiveProperty.Create<{
        heart: number,
        updateAt: number,
    }>(null);
    coin = ReactiveProperty.Create(null);

    temp_coin_diff = ReactiveProperty.Create(null); // 用于临时显示，如果有值，则显示该值

    itemSwapper = ReactiveProperty.Create(null);
    itemMagnet = ReactiveProperty.Create(null);
    itemBalloon = ReactiveProperty.Create(null);

    passedFlags = ReactiveProperty.Create<Set<string>>(new Set()); // 已经通过的key，比如是否弹出某个道具的解锁

    getItemRx(key: ItemKey) {
        switch (key) {
            case 'swapper': return this.itemSwapper;
            case 'magnet': return this.itemMagnet;
            case 'balloon': return this.itemBalloon;
        }
    }

    noAds = ReactiveProperty.Create(null); // 去广告: 0 没有购买， 1已经购买
    noAdsUntil = ReactiveProperty.Create(null); // 去广告直到, 里面是时间戳，在这个时间点之前依然是去广告状态

    infHeartTill = ReactiveProperty.Create(0); // 无限体力直到这个时间点

    unfinishedGame = ReactiveProperty.Create(0); // 未完成的游戏，已经开始，但是没有胜利或失败，重新启动游戏后会检查该字段
    unfinishedGameStartAt = ReactiveProperty.Create(0); // 未完成的游戏，已经开始，但是没有胜利或失败，重新启动游戏后会检查该字段

    iapStat = ReactiveProperty.Create<Partial<Record<IapKey, IapStat>>>({}); // 购买统计,也辅助限制购买逻辑，比如只能购买一次的新手礼包
    iapBuyAt = ReactiveProperty.Create<number>(null);

    freeAdsStat = ReactiveProperty.Create<FreeStat>(null); // 免费广告统计，辅助限制免费广告入口

    stageChest = ReactiveProperty.Create<string>(null); // 需要弹出的宝箱flag

    savingPot = ReactiveProperty.Create<SavingPotInfo>(null); // 存钱罐相关

    task = ReactiveProperty.Create<{ [key: number]: number }>(null); // 任务相关
    taskUpdateAt = ReactiveProperty.Create<number>(null); // 任务更新时间

    luckyWheelSpinCount = ReactiveProperty.Create<number>(0); // 大转盘已转次数
    luckyWheelAt = ReactiveProperty.Create<number>(0); // 大转盘操作时间

    // Thinking打点辅助

    todayLaunchAt = ReactiveProperty.Create(0); // 今日登录时间 (如果发现不是今日，则更新成今日，如果是今日，则不变)
    loginDay = ReactiveProperty.Create(0); // 登录天数

    winStreak = ReactiveProperty.Create(0); // 连胜场次

    stageStat = ReactiveProperty.Create<Record<string, StageStat>>({}); // 关卡统计

    bannerAdTimes = ReactiveProperty.Create(0);

    interstitialAdTimes = ReactiveProperty.Create(0); // 插屏累计次数
    interstitialAt = ReactiveProperty.Create(null); // 距上次时长

    rewardedAdTimes = ReactiveProperty.Create(0); // 激励累计次数

    iapPurchasedTimes = ReactiveProperty.Create(null); // 总付费数
    iapPurchasedPrice = ReactiveProperty.Create(null); // 总付费金额

    firstLoginAt = ReactiveProperty.Create(null); // 首次激活时间点
    launchTimeTotal = ReactiveProperty.Create(0); // 累计运行时间

    af_r30r_logged = ReactiveProperty.Create(false);// af_r30r是否已经上报

    openWay = ReactiveProperty.Create<string>(null); // 上次的OpenWay

    processingProductIds = ReactiveProperty.Create<string[]>([]); // 正在交易的product_id

    // 及时计算出来的值，不需要保存：
    dailyFirstLogin = ReactiveProperty.Create(true); // 是否当日首次登录
    isNew = ReactiveProperty.Create<boolean>(null); // 是否新用户

    launchTime = ReactiveProperty.Create(0); // 运行时间


    useObserves() {
        return [
            this.lang.subscribe(_ => {
                i18n.init(this.lang.value);
                i18n.updateSceneRenderers();
                this.langChangeCommand.execute();
            }),
            this.stageNum.subscribe(_ => {
                thinking_sdk_m.requestUserSet({
                    Level_ID: this.stageNum.value,
                });
                thinking_sdk_m.requestSetSuperProperties({
                    Level_ID: this.stageNum.value,
                });
            }),
            this.coin.subscribe(_ => {
                if (this.coin.value != null) {
                    thinking_sdk_m.requestUserSet({
                        Coins: this.coin.value,
                    });
                    thinking_sdk_m.requestSetSuperProperties({
                        CoinAll: this.coin.value
                    });
                }
            }),
            Merge(this.heart, this.infHeartTill).subscribe(_ => {
                if (this.heart.value != null) {
                    let count = this.getHeartCount();
                    let remain = this.getRemainMsToOverInfHeart();
                    thinking_sdk_m.requestSetSuperProperties({
                        Energy: (remain > 0) ? 1000 : count,
                    });
                }
            }),




            // TODO: 有道具之后加上
            // Merge(this.itemSwapper, this.itemMagnet, this.itemBalloon).subscribe(_ => {
            //     if (this.itemSwapper.value != null && this.itemMagnet.value != null && this.itemBalloon.value != null) {
            //         thinking_sdk_m.requestUserSet({
            //             Booster_Itemdetail: `Swapper:${this.itemSwapper.value}|Magnet:${this.itemMagnet.value}|Balloon:${this.itemBalloon.value}`
            //         });
            //         thinking_sdk_m.requestSetSuperProperties({
            //             Booster_Itemdetail: `Swapper:${this.itemSwapper.value}|Magnet:${this.itemMagnet.value}|Balloon:${this.itemBalloon.value}`
            //         });
            //     }
            // }),

            this.isNew.subscribe(_ => {
                if (this.isNew.value != null) {
                    thinking_sdk_m.requestSetSuperProperties({
                        IsNew: this.isNew.value,
                    });
                }
            }),
            this.noAds.subscribe(_ => {
                if (this.noAds.value != null) {
                    thinking_sdk_m.requestSetSuperProperties({
                        Ads_Status: this.noAds.value == 1 ? 'No_Ads' : 'Normal'
                    });
                }
            }),

            this.iapPurchasedPrice.subscribe(_ => {
                if (this.iapPurchasedPrice.value != null) {
                    thinking_sdk_m.requestSetSuperProperties({
                        pay_money: this.iapPurchasedPrice.value / 100,
                    });
                }
            }),

            this.iapPurchasedTimes.subscribe(_ => {
                if (this.iapPurchasedTimes.value != null) {
                    thinking_sdk_m.requestSetSuperProperties({
                        pay_count: this.iapPurchasedTimes.value,
                    });
                }
            }),

            this.bgmOn.subscribe(_ => {
                thinking_sdk_m.requestUserSet({
                    bgmOn: this.bgmOn.value,
                });
            }),
            this.sfxOn.subscribe(_ => {
                thinking_sdk_m.requestUserSet({
                    sfxOn: this.sfxOn.value,
                });
            }),
            this.vibrateOn.subscribe(_ => {
                thinking_sdk_m.requestUserSet({
                    vibrateOn: this.vibrateOn.value,
                });
            }),

            // MergeASAP(this.iapPurchasedPrice, this.iapPurchasedTimes).subscribe(_ => {
            //     if (this.iapPurchasedPrice.value != null && this.iapPurchasedTimes.value != null) {
            //         thinking_sdk_m.requestSetSuperProperties({
            //             pay_money: this.iapPurchasedPrice.value / 100,
            //             pay_count: this.iapPurchasedTimes.value,
            //         });
            //     }
            // }),
            cloudstorage_sdk_m.gameData.subscribe(this.checkCloudData.bind(this))
        ];
    }

    stringify() {
        return JSON.stringify({
            v: this.v.value,
            data_ver: this.dataVer.value,
            uid: this.uid.value,

            display_name: this.displayName.value,

            bgm: this.bgmOn.value,
            bgm_volume: this.bgmVolume.value,
            sfx: this.sfxOn.value,
            sfx_volume: this.sfxVolume.value,
            vibrate: this.vibrateOn.value,
            full_screen: this.fullScreenOn.value,
            lang: this.lang.value,

            stage_num: this.stageNum.value,
            heart: this.heart.value,
            coin: this.coin.value,

            item_swapper: this.itemSwapper.value,
            item_magnet: this.itemMagnet.value,
            item_balloon: this.itemBalloon.value,

            no_ads: this.noAds.value,
            no_ads_until: this.noAdsUntil.value,

            inf_heart_till: this.infHeartTill.value,

            unfin_game: this.unfinishedGame.value,
            unfin_game_start_at: this.unfinishedGameStartAt.value,

            free_ads_stat: this.freeAdsStat.value,

            stage_chest: this.stageChest.value,
            saving_pot: this.savingPot.value,

            today_launch_at: this.todayLaunchAt.value,
            login_day: this.loginDay.value,
            win_streak: this.winStreak.value,

            stage_stat: this.stageStat.value,

            banner_times: this.bannerAdTimes.value,

            interstitial_times: this.interstitialAdTimes.value,
            interstitial_at: this.interstitialAt.value,

            rewardedad_times: this.rewardedAdTimes.value,

            iap_stat: this.iapStat.value,
            iap_buyat: this.iapBuyAt.value,
            iap_purchased_times: this.iapPurchasedTimes.value,
            iap_purchased_price: this.iapPurchasedPrice.value,

            first_login_at: this.firstLoginAt.value,
            launch_time_total: this.launchTimeTotal.value,

            af_r30r_logged: this.af_r30r_logged.value,

            open_way: this.openWay.value,

            processingProductIds: this.processingProductIds.value,

            task: this.task.value,
            task_at: this.taskUpdateAt.value,

            lucky_wheel_spin_count: this.luckyWheelSpinCount.value,
            lucky_wheel_at: this.luckyWheelAt.value,

            passed_flags: Array.from(this.passedFlags.value),

            // luck_wheel: this.
        });
    }

    correct() {
        jsb_sdk_m.storeLaunchTime(true);
    }

    parse(str) {
        let obj = {} as any;
        if (str != null && str != '') {
            obj = JSON.parse(str);
        }
        if (obj?.uid == null) {
            this.isNew.value = true;
        } else {
            this.isNew.value = false;
        }
        this.v.value = obj?.v ?? 0;
        this.uid.value = obj?.uid ?? UUIDTools.GenUUID();
        this.displayName.value = obj?.display_name;
        this.dataVer.value = obj?.data_ver ?? 0;
        this.bgmOn.value = obj?.bgm ?? false;
        this.bgmVolume.value = obj?.bgm_volume ?? 1;
        this.sfxOn.value = obj?.sfx ?? true;
        this.sfxVolume.value = obj?.sfx_volume ?? 1;
        this.vibrateOn.value = obj?.vibrate ?? true;
        this.fullScreenOn.value = obj?.full_screen ?? false;
        this.lang.value = obj?.lang ?? 'en';
        let targetStageNum = obj?.stage_num ?? 1;
        if (targetStageNum > 0) { // 兼容开发者不小心给关卡号设置错误，比如0，将无法启动
            this.stageNum.value = targetStageNum;
        } else {
            this.stageNum.value = 1;
        }
        let heart = obj?.heart ?? {};
        if (heart.heart == null) heart.heart = DevConfig.HEART_INIT;
        if (heart.updateAt == null) heart.updateAt = timem.now();
        this.heart.value = heart;
        this.coin.value = obj?.coin ?? DevConfig.COIN_INIT;

        this.itemSwapper.value = obj?.item_swapper ?? 0;
        this.itemMagnet.value = obj?.item_magnet ?? 0;
        this.itemBalloon.value = obj?.item_balloon ?? 0;

        this.noAds.value = obj?.no_ads ?? 0;
        this.noAdsUntil.value = obj?.no_ads_until ?? 0;

        this.infHeartTill.value = obj?.inf_heart_till ?? 0;
        this.unfinishedGame.value = obj?.unfin_game ?? 0;
        this.unfinishedGameStartAt.value = obj?.unfin_game_start_at ?? 0;
        this.todayLaunchAt.value = obj?.today_launch_at ?? 0;
        this.loginDay.value = obj?.login_day ?? 0;
        this.winStreak.value = obj?.win_streak ?? 0;
        this.stageStat.value = obj?.stage_stat ?? {};

        this.stageChest.value = obj?.stage_chest ?? null;
        this.savingPot.value = obj?.saving_pot ?? {};

        this.freeAdsStat.value = obj?.free_ads_stat ?? {};

        this.bannerAdTimes.value = obj?.banner_times ?? 0;

        this.interstitialAdTimes.value = obj?.interstitial_times ?? 0;
        this.interstitialAt.value = null;/*obj?.interstitial_at ?? null;*/

        this.rewardedAdTimes.value = obj?.rewardedad_times ?? 0;

        this.iapStat.value = obj?.iap_stat ?? {};
        this.iapBuyAt.value = obj?.iap_buyat ?? null;
        this.iapPurchasedTimes.value = obj?.iap_purchased_times ?? 0;
        this.iapPurchasedPrice.value = obj?.iap_purchased_price ?? 0;

        this.firstLoginAt.value = obj?.first_login_at;
        this.launchTimeTotal.value = obj?.launch_time_total ?? 0;

        this.af_r30r_logged.value = obj?.af_r30r_logged ?? false;

        this.openWay.value = obj?.open_way ?? 'Store';

        this.processingProductIds.value = obj?.processingProductIds ?? [];

        this.task.value = obj?.task ?? {};
        this.taskUpdateAt.value = obj?.task_at ?? timem.now();

        this.luckyWheelSpinCount.value = obj?.lucky_wheel_spin_count ?? 0;
        this.luckyWheelAt.value = obj?.lucky_wheel_at ?? timem.now();

        this.passedFlags.value = new Set(obj?.passed_flags);
    }

    clear() {
        sys.localStorage.removeItem(this.localstorageKey);
    }

    save() {
        this.correct();
        this.v.value++;
        let str = this.stringify();
        sys.localStorage.setItem(this.localstorageKey, str);

        this.log('@save');
        if (cloudstorage_sdk_m.canSave) {
            if (cloudstorage_sdk_m.gameData.value == null) {
                cloudstorage_sdk_m.requestSaveGameData(str);
            } else {
                if (cloudstorage_sdk_m.gameData.value.v < this.v.value) {
                    cloudstorage_sdk_m.requestSaveGameData(str);
                }
            }
        }

        pfm.saveUserDataCommand.execute(str);
    }

    load() {
        let str = sys.localStorage.getItem(this.localstorageKey);
        this.parse(str);
        this.initialize();

        this.log("@load");
        cloudstorage_sdk_m.requestLoadGameData();
    }

    localstorageKey_stageInfo = 'stageInfo_0';
    saveStageInfo(stageInfo: StageInfo) {
        let str = JSON.stringify(stageInfo);
        sys.localStorage.setItem(this.localstorageKey_stageInfo, str);
    }

    loadStageInfo(): StageInfo {
        let str = sys.localStorage.getItem(this.localstorageKey_stageInfo);
        if (str != null || str != '') {
            return JSON.parse(str) as StageInfo;
        }
        return null;
    }

    checkCloudData() { // 检查云端数据
        this.log('@checkCloudData');
        let data = cloudstorage_sdk_m.gameData.value;
        this.log(`@checkCloudData cloud_data_v: ${data?.v ?? 0} local_data_v: ${this.v.value ?? 0}`);
        if (data != null) {
            if (data.v > this.v.value) {
                sys.localStorage.setItem(this.localstorageKey, cloudstorage_sdk_m.gameDataStr.value);
                this.parse(cloudstorage_sdk_m.gameDataStr.value);
                this.initialize();
                game.restart();
            }
        }
    }

    initialize() {
        if (this.dataVer.value != this.CUR_DATA_VER) { // 数据结构版本变化处理
            this.log(`数据结构升级，版本${this.dataVer.value} -> ${this.CUR_DATA_VER}`);
            // TODO: 执行升级
        }

        if (this.unfinishedGame.value > 0) {
            let stageInfo = this.loadStageInfo();
            if (stageInfo != null) {
                this.gameLose(stageInfo, 'force_quit');
            }
        }

        jsb_sdk_m.firstLaunch();

        // 更新今日登录时间
        let recordDayAt = new Date(this.todayLaunchAt.value).setUTCHours(0, 0, 0, 0);
        let currentDayAt = new Date().setUTCHours(0, 0, 0, 0);
        if (recordDayAt == currentDayAt) { // 是同一天
            this.dailyFirstLogin.value = false;
        } else { // 不是同一天，本次是新的一天
            this.todayLaunchAt.value = timem.now();
            this.loginDay.value++;
            this.dailyFirstLogin.value = true;
            this.save();

            thinking_sdk_m.requestUserAdd({
                PlayDays: 1
            });
        }

        thinking_sdk_m.requestLogin(this.uid.value);

        thinking_sdk_m.requestUserSetOnce({
            __date__First_LoginTime: timem.now(),
            FirstVersion: misc_sdk_m.getNativeVersion(),
        });

        thinking_sdk_m.requestUserSet({
            __date__last_game_start_time: timem.now(),
            Level_ID: this.stageNum.value,
            NowVersion: misc_sdk_m.getNativeVersion(),
            Coins: this.coin.value,
            PhoneModel: misc_sdk_m.deviceInfoDic.value?.['phoneModel'],
            OSVersion: misc_sdk_m.deviceInfoDic.value?.['systemName'] + misc_sdk_m.deviceInfoDic.value?.['systemVersion'],
        });
        thinking_sdk_m.requestUserAdd({
            OpenNum: 1,
        });

        // 打开应用打点
        thinking_sdk_m.requestTrack('Start_Login', {
            DailyFirstLogin: this.dailyFirstLogin.value,
            LoginDay: this.loginDay.value,
            IsNew: this.isNew.value,
        });

        if (this.isNew.value) {

            this.firstLoginAt.value = timem.now();
            this.launchTimeTotal.value = 0;
            this.save();

            thinking_sdk_m.requestTrack('Coins_Get', {
                CoinNum: DevConfig.COIN_INIT,
                coinall: udm.coin.value,
                Type: 'Guide',
                Gift_Detail: 'Other',
                IsNew: udm.isNew.value,
                Level_ID: udm.stageNum.value,
            });

            // thinking_sdk_m.requestTrack('Get_Energy', {
            //     Type: "Guide",
            //     EnergyType: 'Num',
            //     EnergyCount: DevConfig.HEART_INIT,
            //     IsNew: udm.isNew.value,
            //     Level_ID: udm.stageNum.value,
            // });

            appsflyer_sdk_m.logEvent('af_Activation');
        }


        // this.scheduleNotification(); // TODO: 确定游戏名称后使用
        this.savingPotRefresh();
    }

    scheduleNotification() {
        // 设定一个今晚的0点的通知,因为免费金币在此时可以领取
        // let date = timem.getCurrentDate();
        // date.setHours(24, 0, 0, 0);
        // notification_sdk_m.requestScheduleNotification(
        //     "free_coin_0",
        //     "Aquapark Craze",
        //     "Free coins are waiting for you!",
        //     date
        // );
        // 设定免费金币通知, 如果有金币可以领取并且不在游戏中，那么10分钟之后弹出通知）
        // let freeAt = udm.freeAdsStat.value?.freeAt ?? 0;
        // let freeAtNextDay = new Date(freeAt);
        // freeAtNextDay.setHours(24, 0, 0, 0);
        // let nowZero = timem.getCurrentDate();
        // if (nowZero > freeAtNextDay) {
        //     let date = timem.getCurrentDate();
        //     date.setMinutes(date.getMinutes() + 10);
        //     notification_sdk_m.requestScheduleNotification(
        //         "free_coin_0",
        //         "Aquapark Craze rewards",
        //         "Free coins are waiting for you!",
        //         date
        //     );
        // }
        // 设定体力满了的通知
        // let heartCount = this.getHeartCount();
        // if (heartCount >= DevConfig.HEART_RESTORE_MAX_COUNT) {
        //     let date = timem.getCurrentDate();
        //     date.setMinutes(date.getMinutes() + 10);
        //     notification_sdk_m.requestScheduleNotification(
        //         "full_energy_0",
        //         "Aquapark Craze",
        //         "Your energy is full!",
        //         date
        //     );
        // } else {
        //     let needHeartCount = DevConfig.HEART_RESTORE_MAX_COUNT - heartCount;
        //     let seconds = needHeartCount * DevConfig.HEART_RESTORE_NEED_SECOND;
        //     let extraMs = timem.now() - (this.heart.value.updateAt ?? timem.now());
        //     let date = new Date();
        //     date.setSeconds(date.getSeconds() + seconds, date.getMilliseconds() + extraMs);
        //     notification_sdk_m.requestScheduleNotification(
        //         "full_energy_0",
        //         "Aquapark Craze",
        //         "Your energy is full!",
        //         date
        //     );
        // }
        // 第二天同一时间
        {
            let date = new Date();
            date.setDate(date.getDate() + 1);
            let cand = [
                `Can you solve the Level ${this.stageNum.value} puzzle faster than friends? 🏁`,
                `What's Behind Level ${this.stageNum.value}? 🔍`,
            ];
            notification_sdk_m.requestScheduleNotification(
                "next_day",
                "Color Slide - Block Puzzle",
                randomChoose(cand),
                date
            );
        }
    }

    // 去广告
    isNoAds() {
        if (this.noAds.value == 1) {
            return true;
        } else if (this.noAdsUntil.value >= 0) {
            if (timem.now() < this.noAdsUntil.value) {
                return true;
            }
        }
        return false;
    }
    getRemainMsToOverNoAds() {
        return this.noAdsUntil.value - timem.now();
    }

    // 体力相关
    // 刷新，需要把时间加上
    refreshHeart() {
        // 距离上次刷新已经过了多久
        let diffMs = timem.now() - this.heart.value.updateAt;
        // 过的这些时间最多可以回复多少心
        let incCountMax = Math.floor(diffMs / (DevConfig.HEART_RESTORE_NEED_SECOND * 1000));
        // 除了回复的心，还多余一些毫秒数
        let remainMs = diffMs % (DevConfig.HEART_RESTORE_NEED_SECOND * 1000);
        // 距离恢复体力的上限，当前还需要多少心
        let needCountMax = Math.max(0, DevConfig.HEART_RESTORE_MAX_COUNT - this.heart.value.heart);
        // 本次只能恢复的心数量
        let realRecoverCount = Math.min(incCountMax, needCountMax);
        // 达到最大值后，刷新使用当前时间
        if (this.heart.value.heart < DevConfig.HEART_RESTORE_MAX_COUNT) {
            this.heart.value.updateAt = timem.now() - remainMs;
        } else {
            this.heart.value.updateAt = timem.now();
        }
        // 把回复量加回 heart
        if (realRecoverCount > 0) {
            this.heart.value.heart += realRecoverCount;
            // 如果没有达到最大值，需要把剩余的毫秒数加回updateAt，相当于在当前updateAt基础上向前推remainMs时间

            this.heart.again();
            this.save();
        }
    }
    // 获取距离下次加心剩余的毫秒数
    getRemainMsToRecoverOneHeart() {
        if (this.heart.value.heart >= DevConfig.HEART_RESTORE_MAX_COUNT) {
            return null;
        } else {
            let diffMs = timem.now() - this.heart.value.updateAt;
            let remainMs = DevConfig.HEART_RESTORE_NEED_SECOND * 1000 - (diffMs % (DevConfig.HEART_RESTORE_NEED_SECOND * 1000));
            return remainMs;
        }
    }
    // 获取当前的心数量
    getHeartCount() {
        this.refreshHeart();
        return this.heart.value.heart;
    }
    // 增加心
    incHeartCount(count: number) {
        this.heart.value.heart += count;
        if (this.heart.value.heart >= DevConfig.HEART_RESTORE_MAX_COUNT) {
            this.heart.value.updateAt = timem.now();
        }
        this.save();
        this.heart.again();
        this.scheduleNotification();
    }
    // 减少心
    decHeartCount(count: number, lastUpdateAt?: number) {
        if (this.heart.value.heart >= DevConfig.HEART_RESTORE_MAX_COUNT) {
            this.heart.value.updateAt = lastUpdateAt ?? timem.now();
        }
        let targetCount = Math.max(0, this.heart.value.heart - count);
        this.heart.value.heart = targetCount;
        this.save();
        this.heart.again();
        this.scheduleNotification();
    }
    // 距离无限体力倒计时剩余的毫秒数
    getRemainMsToOverInfHeart() {
        return this.infHeartTill.value - timem.now();
    }

    // SavingPot 存钱罐
    savingPotRefresh() {
        if (udm.savingPot.value == null) udm.savingPot.value = {} as SavingPotInfo;
        if (udm.savingPot.value.coin == null) udm.savingPot.value.coin = 0;
        if (udm.savingPot.value.expAt == null) udm.savingPot.value.expAt = null;
        if (udm.savingPot.value.expAt != null && timem.now() > udm.savingPot.value.expAt) { // 检查超时清空
            udm.savingPot.value.coin = 0;
            udm.savingPot.value.expAt = null;
        }
        if (udm.savingPot.value.coin >= DevConfig.SAVING_POT.COIN_CAPACITY) {
            udm.savingPot.value.coin = DevConfig.SAVING_POT.COIN_CAPACITY;
            if (udm.savingPot.value.expAt == null) {
                udm.savingPot.value.expAt = timem.now() + DevConfig.SAVING_POT.EXP_SEC * 1000;
            }
        }
    }

    savingPotIncreaseCoin() { // 增加金币
        this.savingPotRefresh();
        let savingPotCoin = udm.savingPot.value.coin;
        savingPotCoin += DevConfig.SAVING_POT.INC_STEP;

        if (udm.savingPot.value.coin < DevConfig.SAVING_POT.UNLOCK_COUNT && savingPotCoin >= DevConfig.SAVING_POT.UNLOCK_COUNT) {
            udm.savingPot.value.autoPopup = true;
        }

        if (savingPotCoin >= DevConfig.SAVING_POT.COIN_CAPACITY) {
            savingPotCoin = DevConfig.SAVING_POT.COIN_CAPACITY;
            if (udm.savingPot.value.coin < DevConfig.SAVING_POT.COIN_CAPACITY) {
                udm.savingPot.value.expAt = timem.now() + DevConfig.SAVING_POT.EXP_SEC * 1000;
            }
        }
        udm.savingPot.value.coin = savingPotCoin;
        udm.savingPot.again();
    }

    savingPotGetRemainRsToReset() { // 获得重置前的剩余时间,null为没有计时,也就是没有存满
        if (udm.savingPot.value.expAt != null) {
            return udm.savingPot.value.expAt - timem.now();
        }
        return null;
    }

    savingPotClaim() { // 领取(购买)金币
        udm.coin.value += udm.savingPot.value.coin;
        udm.savingPot.value.coin = 0;
        udm.savingPot.value.expAt = null;
        udm.savingPot.again();
    }

    luckyWheelSpin() { // 转动大转盘
        this.luckyWheelRefreshStorage();
        if (udm.luckyWheelSpinCount.value < DevConfig.LUCKYWHEEL.DAILY_TIMES) {
            udm.luckyWheelSpinCount.value++; // test inf
            udm.luckyWheelAt.value = timem.now();
            let choosedItem = randomChoose(DevConfig.LUCKYWHEEL.rewards.probability);
            this.provide(choosedItem.provide);
            return choosedItem;
        }
    }

    luckyWheelRefreshStorage() {
        let needClearAt = new Date(udm.luckyWheelAt.value);
        needClearAt.setHours(24, 0, 0, 0);
        if (timem.now() > needClearAt.valueOf()) {
            this.luckyWheelReset();
        }
    }

    luckyWheelReset() { // 重置每日
        udm.luckyWheelAt.value = timem.now();
        udm.luckyWheelSpinCount.value = 0;
        udm.save();
    }

    // 获取距离第二天的毫秒数
    getRemainMsToNextDay() {
        let nextZero = timem.getCurrentDate();
        nextZero.setHours(24, 0, 0, 0);
        return nextZero.valueOf() - timem.now();
    }

    // 游戏真正算开始，设置扣心标记
    gameStartUseHeart() {
        this.unfinishedGame.value = 1; // 标记未完成的游戏
        this.unfinishedGameStartAt.value = timem.now();
        udm.save();
    }

    // 游戏开始
    gameStart(stageInfo: StageInfo) {
        this.log('@udm.game: gameStart>>');
        // this.unfinishedGame.value = 1; // 标记未完成的游戏
        // this.unfinishedGameStartAt.value = timem.now();
        this.saveStageInfo(stageInfo);
        if (this.stageStat.value[stageInfo.stageId] == null) this.stageStat.value[stageInfo.stageId] = {};
        if (this.stageStat.value[stageInfo.stageId].startTimes == null) {
            this.stageStat.value[stageInfo.stageId].startTimes = 0;
            gm.currentLevelType.value = 'New';
            if (appsflyer_sdk_m.Level_Xs.has(stageInfo.stageId)) {
                appsflyer_sdk_m.logEvent(`af_Level_${stageInfo.stageId}`);
            }
        } else {
            this.stageStat.value[stageInfo.stageId].startTimes++;
            gm.currentLevelType.value = 'Continue';
        }
        this.save();
        thinking_sdk_m.requestTrack('Level_Start', {
            Level_ID: stageInfo.stageId,
            level_difficulty: HardStr(stageInfo.hard),
            WinStreak: this.winStreak.value,
            Start_Times: this.stageStat.value?.[stageInfo.stageId]?.startTimes ?? 0,
            IsNew: this.isNew.value,
            slide_times: gm.moveTimes.value,
            initial_time_limit: stageInfo.time ?? 0,
        });
    }

    // 游戏胜利
    gameWin(stageInfo: StageInfo) {
        this.log('@udm.game: gameWin>>');

        // let { Target, Target_Process, Target_Rate } = gm.stageView?.getThinkingTargetSeries();

        this.unfinishedGame.value = 0;
        this.unfinishedGameStartAt.value = null;
        this.winStreak.value++;

        gm.coinAnimatingFlags.add('over_anim');
        udm.coin.value += DevConfig.COIN_COMPLETED_EARN;

        // 关卡宝箱
        // let chestResult = this.checkStageChestProgress(stageInfo.stageId);
        // if (chestResult.chestCurValue == chestResult.chestMaxValue) {
        //     udm.stageChest.value = chestResult.flag;
        // }

        this.savingPotIncreaseCoin();

        thinking_sdk_m.requestTrack('Coins_Get', {
            Level_ID: stageInfo.stageId,
            CoinNum: DevConfig.COIN_COMPLETED_EARN,
            coinall: udm.coin.value,
            Type: 'LevelWin',
            IsNew: this.isNew.value,
            get_type: 'free',
        });

        thinking_sdk_m.requestTrack('Level_Victory', {
            Level_ID: stageInfo.stageId,
            level_difficulty: HardStr(stageInfo.hard),
            WinStreak: this.winStreak.value,
            Start_Times: this.stageStat.value?.[stageInfo.stageId]?.startTimes ?? 0,
            duration: gm.curTime.value,//Math.floor((timem.now() - gm.stageStartAt.value) / 1000),
            left_time: stageInfo.time - gm.curTime.value,
            Coin_Use: gm.coinUse.value,
            slide_times: gm.moveTimes.value,
            ReviveTimes: gm.reviveTimes.value,
            initial_time_limit: stageInfo.time ?? 0,
            IsNew: this.isNew.value,
            // Target,
            // Target_Process,
            // Target_Rate,
            // Item_Swapper_Use: gm.itemUseTimes_Swapper.value,
            // Item_Magnet_Use: gm.itemUseTimes_Magnet.value,
            // Item_Balloon_Use: gm.itemUseTimes_Balloon.value,
        });



        let targetStageNum = Math.min(udm.stageNum.value + 1, DevConfig.STAGE_NUM_MAX);
        if (DevConfig.STAGE_RANDOM_REPEAT) {
            targetStageNum = udm.stageNum.value + 1;
        }
        udm.stageNum.value = targetStageNum;
        udm.save();

        taskm.checkInc('win');

        pfm.updateLeaderboardEntries('stageNum_manual', this.stageNum.value).subscribe();
    }

    checkStageChestProgress(stageId: number, afterWin = true): { chestCurValue: number, chestMaxValue: number, flag: string } {
        let chestCurValue = 0;
        let chestMaxValue = 1;
        let flag = null;
        if (afterWin) {
            if (stageId <= 2) {
                chestCurValue = stageId;
                chestMaxValue = 2;
                flag = 'stage2';
            } else if (stageId <= 5) {
                chestCurValue = stageId - 2;
                chestMaxValue = 3;
                flag = 'stage5';
            } else {
                chestCurValue = (stageId - 1) % 5 + 1;
                chestMaxValue = 5;
                flag = 'stageMod5';
            }
        } else {
            if (stageId <= 2) {
                chestCurValue = stageId - 1;
                chestMaxValue = 2;
                flag = 'stage2';
            } else if (stageId <= 5) {
                chestCurValue = stageId - 3;
                chestMaxValue = 3;
                flag = 'stage5';
            } else {
                chestCurValue = (stageId - 1) % 5;
                chestMaxValue = 5;
                flag = 'stageMod5';
            }
        }
        return { chestCurValue, chestMaxValue, flag };
    }

    // 游戏时间到弹窗，此时不算失败，因为玩家可以选择续命
    gameLosePopup(stageInfo: StageInfo, reason?: 'stagetimeup') {
        this.log('@gameLosePopup reason:', reason);
        gm.losePopupReason.value = reason;

        // let { Target, Target_Process, Target_Rate } = gm.stageView?.getThinkingTargetSeries();
        thinking_sdk_m.requestTrack('level_fail', {
            Level_ID: stageInfo.stageId,
            level_difficulty: HardStr(stageInfo.hard),
            WinStreak: this.winStreak.value,
            Start_Times: this.stageStat.value?.[stageInfo.stageId]?.startTimes ?? 0,
            duration: gm.curTime.value,
            left_time: stageInfo.time - gm.curTime.value,
            Coin_Use: gm.coinUse.value,
            slide_times: gm.moveTimes.value,
            ReviveTimes: gm.reviveTimes.value,
            initial_time_limit: stageInfo.time ?? 0,
            IsNew: this.isNew.value,
            // Target,
            // Target_Process,
            // Target_Rate,
            // Item_Swapper_Use: gm.itemUseTimes_Swapper.value,
            // Item_Magnet_Use: gm.itemUseTimes_Magnet.value,
            // Item_Balloon_Use: gm.itemUseTimes_Balloon.value,
        });
    }

    gameLose(stageInfo: StageInfo, reason: 'lose_quit' | 'lose_restart' | 'pause_restart' | 'quit' | 'restart' | 'force_quit') {
        this.log('@udm.game: gameLose>>', reason);
        // let { Target, Target_Process, Target_Rate } = gm.stageView?.getThinkingTargetSeries();


        if (['pause_restart', 'restart'].includes(reason)) {
            thinking_sdk_m.requestTrack('level_reset', {
                Level_ID: stageInfo.stageId,
                level_difficulty: HardStr(stageInfo.hard),
                WinStreak: this.winStreak.value,
                Start_Times: this.stageStat.value?.[stageInfo.stageId]?.startTimes ?? 0,
                reset_time: gm.curTime.value,
                left_time: stageInfo.time - gm.curTime.value,
                Coin_Use: gm.coinUse.value,
                slide_times: gm.moveTimes.value,
                initial_time_limit: stageInfo.time ?? 0,
                IsNew: this.isNew.value,

                // TimeLimit: stageInfo.time ?? 0,
                // TimeRemain: stageInfo.time - gm.curTime.value,
                // ReviveTimes: gm.reviveTimes.value,
                // MoveTimes: gm.moveTimes.value,
                // Target,
                // Target_Process,
                // Target_Rate,
                // Item_Swapper_Use: gm.itemUseTimes_Swapper.value,
                // Item_Magnet_Use: gm.itemUseTimes_Magnet.value,
                // Item_Balloon_Use: gm.itemUseTimes_Balloon.value,
            });
        } else if (['lose_restart', 'lose_quit', 'quit', 'force_quit'].includes(reason)) {
            let CType = 'Quit';
            if (reason == 'force_quit') CType = 'ForceQuit';
            else if (reason == 'lose_quit') CType = 'Fail'; // 不存在了
            else if (reason == 'lose_restart') CType = 'Fail';
            thinking_sdk_m.requestTrack('level_quit', {
                Level_ID: stageInfo.stageId,
                level_difficulty: HardStr(stageInfo.hard),
                WinStreak: this.winStreak.value,
                Start_Times: this.stageStat.value?.[stageInfo.stageId]?.startTimes ?? 0,
                duration: gm.curTime.value,//Math.floor((timem.now() - gm.stageStartAt.value) / 1000),
                left_time: stageInfo.time - gm.curTime.value,
                Coin_Use: gm.coinUse.value,
                slide_times: gm.moveTimes.value,
                CType: CType,
                ReviveTimes: gm.reviveTimes.value,
                initial_time_limit: stageInfo.time ?? 0,
                IsNew: this.isNew.value,
                // Target,
                // Target_Process,
                // Target_Rate,
                // Item_Swapper_Use: gm.itemUseTimes_Swapper.value,
                // Item_Magnet_Use: gm.itemUseTimes_Magnet.value,
                // Item_Balloon_Use: gm.itemUseTimes_Balloon.value,
            });
        }

        this.winStreak.value = 0;
        this.unfinishedGame.value = 0;

        // 不扣心
        // 
        // if (this.getRemainMsToOverInfHeart() > 0) { // 无限体力
        // } else {
        //     if (reason.endsWith('force_quit')) {
        //         udm.decHeartCount(1, udm.unfinishedGameStartAt.value);
        //     } else {
        //         udm.decHeartCount(1);
        //     }

        //     let Energy = udm.getHeartCount();
        //     if (this.getRemainMsToOverInfHeart() > 0) {
        //         Energy = 1000;
        //     }
        //     thinking_sdk_m.requestTrack('Use_Energy', {
        //         Energy,
        //         IsNew: this.isNew.value,
        //         Level_ID: stageInfo.stageId,
        //     });
        // }

        this.unfinishedGameStartAt.value = null;
        this.save();
    }

    gameRevive(stageInfo: StageInfo, reason: 'Gold' | 'Ad', coinUse: number) {

        // let { Target, Target_Process, Target_Rate, TilesTypeNum } = gm.stageView?.getThinkingTargetSeries();

        gm.reviveTimes.value++;
        udm.coin.value -= coinUse;
        gm.coinUse.value += coinUse;
        gm.coinUseForRevive.value += coinUse;
        udm.save();

        thinking_sdk_m.requestTrack('Coins_Use', {
            Level_ID: udm.stageNum.value,
            CoinNum: coinUse,
            coinall: udm.coin.value,
            CoinType: 'revive',
            IsNew: udm.isNew.value,
        });

        thinking_sdk_m.requestTrack('level_revive', {
            Level_ID: stageInfo.stageId,
            level_difficulty: HardStr(stageInfo.hard),
            WinStreak: udm.winStreak.value,
            Start_Times: udm.stageStat.value?.[stageInfo.stageId]?.startTimes ?? 0,
            revive_type: reason,
            used_coin_revive: gm.coinUseForRevive.value,
            Coin_Use: gm.coinUse.value,
            slide_times: gm.moveTimes.value,
            initial_time_limit: stageInfo.time ?? 0,
            IsNew: udm.isNew.value,
            ReviveTimes: gm.reviveTimes.value,
            duration: gm.curTime.value,


            // TilesTypeNum,
            // Target,
            // Target_Process,
            // Target_Rate,
            // TimeLimit: stageInfo.time ?? 0,
            // TimeRemain: stageInfo.time - gm.curTime.value,
            // DeathType: gm.losePopupReason.value,
            // MoveTimes: gm.moveTimes.value,
            // Item_Swapper_Use: gm.itemUseTimes_Swapper.value,
            // Item_Magnet_Use: gm.itemUseTimes_Magnet.value,
            // Item_Balloon_Use: gm.itemUseTimes_Balloon.value,
            // Coin_Use: gm.coinUse.value,
        });
    }

    // 开始计时前返回
    // gameBack(stageInfo: StageInfo) {
    //     this.log('@udm.game: gameBack>>');

    //     thinking_sdk_m.requestTrack('Level_Quit', {
    //         Level_ID: stageInfo.stageId,
    //         LevelType: gm.currentLevelType.value,
    //         DifficultyLevel: stageInfo.hard ?? 0,
    //         WinStreak: this.winStreak.value,
    //         Start_Times: this.stageStat.value?.[stageInfo.stageId]?.startTimes ?? 0,
    //         // TimeLimit: stageInfo.time ?? 0,
    //         // TimeRemain: gm.totalTime.value - gm.curTime.value,
    //         // Target: gm.stage3dView?.getThinkingTarget(),
    //         // Target_process: gm.stage3dView?.getThinkingTargetProcess(),
    //         // Target_Rate: gm.stage3dView?.getThinkingTargetRate(),
    //         // duration: gm.curTime.value,
    //         CType: 'Quit',
    //         // ReviveTimes: gm.reviveTimes.value,
    //         // Add_Time: gm.addTime.value,
    //         // ItemTime_Use: gm.itemUseTimes_frozen.value,
    //         // ItemCrush_Use: gm.itemUseTimes_hammer.value,
    //         // Coin_Use: gm.coinUse.value,
    //         IsNew: this.isNew.value,
    //     });

    //     this.unfinishedGame.value = 0;
    //     this.unfinishedGameStartAt.value = null;
    //     this.save();
    // }

    // 发放iap商品
    provide(iapProductInfo: Partial<ProvideInfo>) {
        this.log('@provide', iapProductInfo);
        if (iapProductInfo.coin > 0) {
            this.coin.value += iapProductInfo.coin;
        }
        if (iapProductInfo.swapper > 0) {
            this.itemSwapper.value += iapProductInfo.swapper;
        }
        if (iapProductInfo.magnet > 0) {
            this.itemMagnet.value += iapProductInfo.magnet;
        }
        if (iapProductInfo.balloon > 0) {
            this.itemBalloon.value += iapProductInfo.balloon;
        }
        if (iapProductInfo.noads > 0) {
            this.noAds.value = 1;
        }
        if (iapProductInfo.inf_heart_sec > 0) {
            let remainMs = this.getRemainMsToOverInfHeart();
            if (remainMs > 0) {
                this.infHeartTill.value = timem.now() + iapProductInfo.inf_heart_sec * 1000 + remainMs;
            } else {
                this.infHeartTill.value = timem.now() + iapProductInfo.inf_heart_sec * 1000;
            }
        }
        if (iapProductInfo.noads_sec > 0) {
            let fromSec = Math.max(this.noAdsUntil.value, timem.now());
            this.noAdsUntil.value = fromSec + iapProductInfo.noads_sec * 1000;
        }
        if (iapProductInfo.dynamic_reward_id == 1) {
            this.savingPotClaim();
        }
        if (iapProductInfo.dynamic_reward_id == 2) {
            // TODO: 添加每日任务宝箱标记
            this.stageChest.value = 'daily_total';
        }
        this.save();
    }

}

export function HardStr(hard: number) {
    if (hard >= 2) return 'superhard';
    if (hard >= 1) return 'hard';
    return 'normal';
}

globalThis.UserDataManager = UserDataManager;

export const udm = UserDataManager.instance = new UserDataManager();